/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: CDS.java,v $
 *
 */

package com.cidero.upnp;

import java.util.List;
import java.util.logging.Logger;

import com.cidero.util.UTF8Util;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *  CDS class holds constants and static functions related
 *  to content directory class heirarchy
 */
public class CDS
{
  private static Logger logger = Logger.getLogger("com.cidero.upnp");

  public static final String DIDL_LITE_HEADER = "<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">\n";

  public static final String DIDL_LITE_TRAILER = "</DIDL-Lite>";

  /**
   * Utility function to extract fixed media path from the UPNP objectId
   *
   *  object id's are similar to:
   *
   *   0/Music/Artists$Artist_<artistId>/Album_<albumId>
   *
   *  where artistId and albumId are actual numbers, typically the unique
   *  ids generated by the SQL database. 
   *
   *  The fixed portion of the media path is the string up to and including
   *  the '$'. The variable part is everything after that. If there is no
   *  '$', then the whole string is considered to be a fixed media path
   *  
   */
  public static String getFixedMediaPath( String objId )
  {
    int sepIndex;
    
    if( (sepIndex = objId.indexOf('$')) == -1 )
      return objId;
    else
      return objId.substring( 0, sepIndex+1 );
  }

  public static String getVariableMediaPath( String objId )
  {
    int sepIndex;
    
    if( (sepIndex = objId.indexOf('$')) != -1 )
    {
      String varPath = objId.substring( sepIndex+1 );
      if( varPath.length() > 0 )
        return varPath;
    }

    return null;
  }
  
  /**
   *   Given a variable path such as "Artist_1/Album_1",
   *   return the type portion of the tail element ("Album")
   *   If the path is "Artist_1/Album_1/Track_1", return "Track"
   */
  public static String getVariableMediaPathType( String varPath )
  {
    String tail;
    int index;

    if( (index = varPath.lastIndexOf('/')) == -1 )
      tail = varPath;
    else
      tail = varPath.substring( index+1 );

    if( (index = tail.indexOf('_')) == -1 )
      return tail;
    else
      return tail.substring( 0, index );
  }

  /**
   *   Given a variable path such as "Artist_1/Album_1",
   *   return the id portion of the tail element ("1")
   */
  public static int getVariableMediaPathId( String varPath )
  {
    String tail;
    int index;
    
    if( (index = varPath.lastIndexOf('/')) == -1 )
      tail = varPath;
    else
      tail = varPath.substring( index+1 );

    if( (index = tail.indexOf('_')) == -1 )
      return -1;
    else
      return Integer.parseInt( tail.substring( index+1 ) );
  }
  
  /**
   * Convert a list of UPNP objects to a DIDL fragment
   *
   * Only return the object in the subset of the list specified by
   * startIndex, reqCount.  In some cases, reqCount may exceed the
   * number of available objects, in which case the returned list is
   * simply truncated
   */
  public static String toDIDL( List objList, String filter,
                               int startIndex, int reqCount, int[] numMatches,
                               String parentId, String objIdPrefix )
  {
    StringBuffer buf = new StringBuffer();
    
    buf.append("<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">\n");

    // TVersity/Roku compat testing...
    //buf.append("<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:tversity=\"http://www.tversity.com/XMLSchemas/upnp/cds/\" >\n");


    // Return empty DIDL-Lite fragment for null incoming object list 
    if( objList == null )
    {
      buf.append("</DIDL-Lite>\n");
      numMatches[0] = 0;
      return buf.toString();
    }

    // Note: If reqCount is 0, process all remaining entries after startIndex
    int endIndex = startIndex + reqCount;
    if( (endIndex > objList.size()) || (reqCount == 0) )
      endIndex = objList.size();

    CDSFilter filterObj = new CDSFilter( filter );
    
    for( int n = startIndex ; n < endIndex ; n++ )
    {
      CDSObject obj = (CDSObject)objList.get(n);

      // If parentId and objIdPrefix specified, modify Id's as appropriate
      if( parentId != null )
      {
        obj.setParentId( parentId );
        obj.setId( parentId + objIdPrefix + obj.getNumericalId() );
      }

      buf.append( obj.toXML( filterObj ) );
      buf.append( "\n" );
    }

    buf.append("</DIDL-Lite>\n");

    numMatches[0] = endIndex - startIndex;

    return buf.toString();
  }

  /**
   * Convert single UPNP object to a DIDL fragment
   */
  public static String toDIDL( CDSObject obj, String filter )
  {
    StringBuffer buf = new StringBuffer();
    
    buf.append("<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">\n");

    buf.append( obj.toXML( new CDSFilter( filter) ) );
    buf.append( "\n" );

    buf.append("</DIDL-Lite>\n");

    String tmp = buf.toString();

    //System.out.println("~~~~~~~~~~~ Checking toDIDL string for UTF-8");
    //UTF8Util.checkForMultiByteChars( tmp );

    return tmp;
  }

  /**
   * Convert single UPNP object to a DIDL fragment, suppressing output
   * of all resources except the selected one
   */
  public static String toDIDL( CDSObject obj, String filter,
                               CDSResource selectedResource )
  {
    StringBuffer buf = new StringBuffer();
    
    buf.append("<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">\n");

    obj.setSelectedResource( selectedResource );
    buf.append( obj.toXML( new CDSFilter( filter) ) );
    obj.setSelectedResource( null );

    buf.append( "\n" );

    buf.append("</DIDL-Lite>\n");

    String tmp = buf.toString();

    //System.out.println("~~~~~~~~~~~ Checking toDIDL string for UTF-8");
    //UTF8Util.checkForMultiByteChars( tmp );

    return tmp;
  }

  public static String getParentId( String objId )
  {
    int index = objId.lastIndexOf("/");
    int index2 = objId.lastIndexOf("$");
    
    if( index > index2 )
      return objId.substring(0, index);
    else
      return objId.substring(0, index2+1);   // Include colon in this case
  }
  
  /**
   *  Convert the object to DIDL, and set the obj/parent Ids based on 
   *  the objId argument
   */
  public static String toDIDL( CDSObject obj, String objId,
                               String filter )
  {
    StringBuffer buf = new StringBuffer();
    
    buf.append("<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n");

    obj.setId( objId );
    obj.setParentId( CDS.getParentId( objId ) );

    buf.append( obj.toXML( new CDSFilter( filter) ) );
    buf.append( "\n" );

    buf.append("</DIDL-Lite>\n");

    return buf.toString();
  }


  public static String getSingleTextNodeValue( Node node )
  {
    NodeList children = node.getChildNodes();

    //System.out.println( "nChildren = " + children.getLength() );

    for( int n = 0 ; n < children.getLength() ; n++ )
    {
      String nodeName = children.item(n).getNodeName();

      //System.out.println( "Inner Node is: " + nodeName );
    }
    
    if( children.getLength() != 1 )  // blank text item
    {
      //System.out.println("Warning: missing or non-singular text node");
      return "";
    }
    
    if( children.item(0).getNodeType() != Node.TEXT_NODE )
    {
      logger.warning("unexpected node type");
      return null;
    }
    
    return (String)children.item(0).getNodeValue();
    
  }

  public static String getAttrAsString( NamedNodeMap attributes,
                                        String attrName ) 
  {
    Node node = attributes.getNamedItem( attrName );
    if( node == null )
      return null;
    
    return node.getNodeValue();
  }

  public static int getAttrAsInt( NamedNodeMap attributes,
                                  String attrName ) 
  {
    Node node = attributes.getNamedItem( attrName );
    if( node == null )
      return -1;

    return Integer.parseInt( node.getNodeValue() );
  }

  public static long getAttrAsLong( NamedNodeMap attributes,
                                    String attrName ) 
  {
    Node node = attributes.getNamedItem( attrName );
    if( (node == null) || (node.getNodeValue().length() == 0) )
      return -1;

    return Long.parseLong( node.getNodeValue() );
  }
  
  /**
   * Encode a URL by replacing spaces (add puncuation later) with 
   * '%20'.  The URLEncoder class uses '+' and also replaces '/' with
   * '%27', which seems to cause problems for the Intel tools - hence
   * the (seeming) need for this customized version
   */   
  public static String encodeURL( String url )
  {
    return url.replaceAll( " ", "%20" );
  }


  public static void main( String[] args )
  {
    String fixed;
    String variable;

    fixed = getFixedMediaPath("0/Music/Artists$Albums/Artist_1");
    //fixed = getFixedMediaPath("0/Music/Artists");
    variable = getVariableMediaPath("0/Music/Artists$Albums/Artist_1");
    //variable = getVariableMediaPath("0/Music/Artists");

    //    try 
    //    {
    System.out.println( "Encoded URL: " + 
                        CDS.encodeURL("/hell'o there" ) ); 
      //    }
      //    catch( UnsupportedEncodingException e )
      //    {
      //      System.out.println( e );
      //    }
    
    System.out.println("Fixed: " + fixed );
    System.out.println("Variable: " + variable );
    System.out.println("VariableType: " + getVariableMediaPathType(variable) );
    System.out.println("VariableId: " + getVariableMediaPathId(variable) );
  }
  
}
